﻿using System;
using System.Collections.Generic;

namespace TLang.Parsers
{
    using Ast;

    public static class Parser
    {
        public static Node Parse(string text, String file)
        {
            PreParser preparser = new PreParser(text, file);
            Node prenode = preparser.Parse();
            return ParseNode(prenode);
        }

        public static Node ParseNode(Node prenode)
        {
            if (prenode is DotNode dotNode)
            {
                List<Node> elements = ParseList(dotNode.Elements);
                return new DotNode(elements, dotNode.File, dotNode.Start, dotNode.End, dotNode.Line, dotNode.Col);
            }
            else if (!(prenode is TupleNode))
            {
                // Case 1: node is not of form (..) or [..], return the node itself
                return prenode;
            }
            else
            {
                // Case 2: node is of form (..) or [..]
                TupleNode tuple = ((TupleNode)prenode);
                List<Node> elements = tuple.Elements;

                if (tuple.Open.IsDelimType(Constants.SquareBegin))
                {
                    // Case 2.1: node is of form [..]
                    return new VectorLiteralNode(ParseList(elements), tuple.File, tuple.Start, tuple.End, tuple.Line, tuple.Col);
                }
                else if (tuple.Open.IsDelimType(Constants.CurlyBegin))
                {
                    // Case node is form {}
                    return new RecordLiteralNode(ParseList(elements), tuple.File, tuple.Start, tuple.End, tuple.Line, tuple.Col);
                }
                else
                {
                    // Case 2.2: node is (..)
                    if (elements.IsEmpty())
                    {
                        // Case 2.2.1: node is (). This is not allowed
                        throw new ParserException("syntax error", tuple);
                    }
                    else
                    {
                        // Case 2.2.2: node is of form (keyword ..)
                        Node keyNode = elements[0];

                        if (keyNode is NameNode)
                        {
                            switch (((NameNode)keyNode).Id)
                            {
                                case Constants.SeqKeyword:
                                    return ParseSeq(tuple);

                                case Constants.IfKeyword:
                                    return ParseIf(tuple);

                                case Constants.CondKeyword:
                                    return ParseCond(tuple);

                                case Constants.CaseKeyword:
                                    return ParseCase(tuple);

                                case Constants.ForKeyword:
                                    return ParseFor(tuple);

                                case Constants.WhileKeyword:
                                    return ParseWhile(tuple);

                                case Constants.DefKeyword:
                                    return ParseDef(tuple);

                                case Constants.SetKeyword:
                                    return ParseSet(tuple);

                                case Constants.DeclareKeyword:
                                    return ParseDeclare(tuple);

                                case Constants.FunKeyword:
                                    return ParseFun(tuple);

                                case Constants.RecordKeyword:
                                    return ParseRecordDef(tuple);

                                case Constants.AndKeyword:
                                    return ParseAndNode(tuple);

                                case Constants.OrKeyword:
                                    return ParseOrNode(tuple);

                                case Constants.IncKeyword:
                                    return ParseIncNode(tuple, isDec: false);

                                case Constants.DecKeyword:
                                    return ParseIncNode(tuple, isDec: true);

                                default:
                                    return ParseCall(tuple);
                            }
                        }
                        else
                        {
                            // applications whose operator is not a name
                            // e.g. ((foo 1) 2)
                            return ParseCall(tuple);
                        }
                    }
                }
            }
        }

        private static SeqNode ParseSeq(TupleNode tuple)
        {
            List<Node> elements = tuple.Elements;
            List<Node> statements = ParseList(elements.SubList(1, elements.Count));
            return new SeqNode(statements, tuple.File, tuple.Start, tuple.End, tuple.Line, tuple.Col);
        }

        private static AndNode ParseAndNode(TupleNode tuple)
        {
            List<Node> elements = tuple.Elements.SubList(1, tuple.Elements.Count);
            if (elements.Count < 2)
            {
                throw new ParserException("incorrect format of &&", tuple);
            }
            return new AndNode(ParseList(elements), tuple.File, tuple.Start, tuple.End, tuple.Line, tuple.Col);
        }

        private static OrNode ParseOrNode(TupleNode tuple)
        {
            List<Node> elements = tuple.Elements.SubList(1, tuple.Elements.Count);
            if (elements.Count < 2)
            {
                throw new ParserException("incorrect format of ||", tuple);
            }
            return new OrNode(ParseList(elements), tuple.File, tuple.Start, tuple.End, tuple.Line, tuple.Col);
        }

        private static IncNode ParseIncNode(TupleNode tuple, bool isDec)
        {
            List<Node> elements = tuple.Elements.SubList(1, tuple.Elements.Count);
            if (elements.Count != 1 && elements.Count != 2)
            {
                throw new ParserException($"incorrect format of {tuple.Elements[0].ToString()}", tuple);
            }
            Node node = ParseNode(elements[0]);
            Node n = new IntNumNode("1", tuple.File,tuple.Start, tuple.End, tuple.Line, tuple.Col);
            if (elements.Count == 2)
            {
                n = elements[1];
            }
            return new IncNode(node, n, isDec, tuple.File, tuple.Start, tuple.End, tuple.Line, tuple.Col);
        }

        private static IfNode ParseIf(TupleNode tuple)
        {
            List<Node> elements = tuple.Elements;
            if (elements.Count != 4)
            {
                throw new ParserException("incorrect format of if", tuple);
            }
            Node test = ParseNode(elements[1]);
            Node then = ParseNode(elements[2]);
            Node orElse = ParseNode(elements[3]);

            return new IfNode(test, then, orElse, tuple.File, tuple.Start, tuple.End, tuple.Line, tuple.Col);
        }

        private static CondNode ParseCond(TupleNode tuple)
        {
            List<Node> elements = tuple.Elements;
            if (elements.Count < 2)
            {
                throw new ParserException("incorrect format of cond", tuple);
            }
            List<Tuple<Node, Node>> paramTuples = new List<Tuple<Node, Node>>();
            for (int i = 1; i < elements.Count; i++)
            {
                TupleNode tupleNode = elements[i] as TupleNode;
                if (tupleNode == null || tupleNode.Elements.Count != 2)
                {
                    throw new ParserException("incorrect format of cond", tupleNode);
                }
                paramTuples.Add(new Tuple<Node, Node>(ParseNode(tupleNode.Elements[0]), ParseNode(tupleNode.Elements[1])));
            }
            if (paramTuples.Count == 0)
            {
                throw new ParserException("incorrect format of cond", tuple);
            }
            return new CondNode(paramTuples, tuple.File, tuple.Start, tuple.End, tuple.Line, tuple.Col);
        }

        private static CaseNode ParseCase(TupleNode tuple)
        {
            List<Node> elements = tuple.Elements;
            if (elements.Count < 3)
            {
                throw new ParserException("incorrect format of cond", tuple);
            }
            Node node = ParseNode(elements[1]);
            List<Tuple<Node, Node>> paramTuples = new List<Tuple<Node, Node>>();
            for (int i = 2; i < elements.Count; i++)
            {
                TupleNode tupleNode = elements[i] as TupleNode;
                if (tupleNode == null || tupleNode.Elements.Count != 2)
                {
                    throw new ParserException("incorrect format of cond", tupleNode);
                }
                paramTuples.Add(new Tuple<Node, Node>(ParseNode(tupleNode.Elements[0]), ParseNode(tupleNode.Elements[1])));
            }
            if (paramTuples.Count == 0)
            {
                throw new ParserException("incorrect format of cond", tuple);
            }
            return new CaseNode(node, paramTuples, tuple.File, tuple.Start, tuple.End, tuple.Line, tuple.Col);
        }

        private static ForNode ParseFor(TupleNode tuple)
        {
            List<Node> elements = tuple.Elements;
            if (elements.Count < 5)
            {
                throw new ParserException("incorrect format of for", tuple);
            }
            return new ForNode(ParseList(elements.SubList(1, elements.Count)), tuple.File, tuple.Start, tuple.End, tuple.Line, tuple.Col);
        }

        private static WhileNode ParseWhile(TupleNode tuple)
        {
            List<Node> elements = tuple.Elements;
            if (elements.Count < 3)
            {
                throw new ParserException("incorrect format of while", tuple);
            }
            return new WhileNode(ParseList(elements.SubList(1, elements.Count)), tuple.File, tuple.Start, tuple.End, tuple.Line, tuple.Col);
        }

        private static DefNode ParseDef(TupleNode tuple)
        {
            List<Node> elements = tuple.Elements;
            if (elements.Count != 3 && elements.Count != 4)
            {
                throw new ParserException("incorrect format of definition", tuple);
            }
            Node pattern = ParseNode(elements[1]);
            if (!(pattern is NameNode) && !(pattern is VectorLiteralNode) && !(pattern is RecordLiteralNode))
            {
                if (pattern is CallNode)
                {
                    throw new ParserException("incorrect format of definition:" + pattern.ToString() + " can't be a CallNode", tuple);
                }
                if (pattern is DotNode)
                {
                    throw new ParserException($"variable name:'{pattern}' can't contains '{Constants.DotChar}'", tuple);
                }
                throw new ParserException("incorrect format of definition:" + pattern.ToString() + "must be a name or [] or {} format", tuple);
            }
            Node type = null;
            Node value;
            if (elements.Count == 3)
            {
                value = ParseNode(elements[2]);
            }
            else
            {
                type = elements[2];
                if (!(type is KeywordNode))
                {
                    throw new ParserException("incorrect format of definition:" + type.ToString() + " must be a keyword, a keyword start with ':'", tuple);
                }
                value = ParseNode(elements[3]);
            }
            return new DefNode(pattern, type, value, tuple.File, tuple.Start, tuple.End, tuple.Line, tuple.Col);
        }

        private static SetNode ParseSet(TupleNode tuple)
        {
            List<Node> elements = tuple.Elements;
            if (elements.Count != 3)
            {
                throw new ParserException("incorrect format of definition", tuple);
            }
            Node pattern = ParseNode(elements[1]);
            Node value = ParseNode(elements[2]);
            return new SetNode(pattern, value, tuple.File, tuple.Start, tuple.End, tuple.Line, tuple.Col);
        }

        private static DeclareNode ParseDeclare(TupleNode tuple)
        {
            List<Node> elements = tuple.Elements;
            if (elements.Count < 2)
            {
                throw new ParserException("syntax error in record type definition", tuple);
            }
            Scope properties = ParseProperties(elements.SubList(1, elements.Count));
            return new DeclareNode(properties, tuple.File, tuple.Start, tuple.End, tuple.Line, tuple.Col);
        }

        private static Node ParseFun(TupleNode tuple)
        {
            List<Node> elements = tuple.Elements;

            if (elements.Count < 3)
            {
                throw new ParserException("syntax error in function definition", tuple);
            }
            bool isFunDef = elements[1] is NameNode;
            Node pattern = isFunDef ? ParseNode(elements[1]) : null;
            // construct parameter list
            Node preParams = isFunDef ? elements[2] : elements[1];
            if (!(preParams is TupleNode))
            {
                throw new ParserException("incorrect format of parameters: " + preParams.ToString(), preParams);
            }

            // parse the parameters, test whether it's all names or all tuples
            bool hasName = false;
            bool hasTuple = false;
            List<NameNode> paramNames = new List<NameNode>();
            List<Node> paramTuples = new List<Node>();

            foreach (Node p in ((TupleNode)preParams).Elements)
            {
                if (p is NameNode)
                {
                    hasName = true;
                    paramNames.Add((NameNode)p);
                }
                else if (p is TupleNode)
                {
                    hasTuple = true;
                    List<Node> argElements = ((TupleNode)p).Elements;
                    if (argElements.Count == 0)
                    {
                        throw new ParserException("illegal argument format: " + p.ToString(), p);
                    }
                    if (!(argElements[0] is NameNode))
                    {
                        throw new ParserException("illegal argument name : " + argElements[0], p);
                    }

                    NameNode name = (NameNode)argElements[0];
                    if (!name.Id.Equals(Constants.ReturnArrow))
                    {
                        paramNames.Add(name);
                    }
                    paramTuples.Add(p);
                }
            }

            if (hasName && hasTuple)
            {
                throw new ParserException("parameters must be either all names or all tuples: " +
                        preParams.ToString(), preParams);
            }

            Scope properties;
            if (hasTuple)
            {
                properties = ParseProperties(paramTuples);
            }
            else
            {
                properties = null;
            }

            // construct body
            List<Node> statements = ParseList(elements.SubList(isFunDef ? 3 : 2, elements.Count));
            int start = statements[0].Start;
            int end = statements[statements.Count - 1].End;
            Node body = new SeqNode(statements, tuple.File, start, end, tuple.Line, tuple.Col);

            if (isFunDef)
            {
                Node funNode = new FunNode(paramNames, properties, body,
                    tuple.File, tuple.Start, tuple.End, tuple.Line, tuple.Col);
                return new DefNode(pattern, null, funNode, tuple.File, tuple.Start, tuple.End, tuple.Line, tuple.Col);
            }
            return new FunNode(paramNames, properties, body,
                    tuple.File, tuple.Start, tuple.End, tuple.Line, tuple.Col);
        }

        private static RecordDefNode ParseRecordDef(TupleNode tuple)
        {
            List<Node> elements = tuple.Elements;
            if (elements.Count < 2)
            {
                throw new ParserException("syntax error in record type definition", tuple);
            }

            Node name = elements[1];
            Node maybeParents = elements[2];

            List<NameNode> parents;
            List<Node> fields;

            if (!(name is NameNode))
            {
                throw new ParserException("syntax error in record name: " + name.ToString(), name);
            }

            // check if there are parents (record A (B C) ...)
            if (maybeParents is TupleNode &&
                    ((TupleNode)maybeParents).Open.IsDelimType(Constants.ParenBegin))
            {
                List<Node> parentNodes = ((TupleNode)maybeParents).Elements;
                parents = new List<NameNode>();
                foreach (Node p in parentNodes)
                {
                    if (!(p is NameNode))
                    {
                        throw new ParserException("parents can only be names", p);
                    }
                    parents.Add((NameNode)p);
                }
                fields = elements.SubList(3, elements.Count);
            }
            else
            {
                parents = null;
                fields = elements.SubList(2, elements.Count);
            }

            Scope properties = ParseProperties(fields);
            return new RecordDefNode((NameNode)name, parents, properties, tuple.File,
                    tuple.Start, tuple.End, tuple.Line, tuple.Col);
        }

        private static CallNode ParseCall(TupleNode tuple)
        {
            List<Node> elements = tuple.Elements;
            Node func = ParseNode(elements[0]);
            List<Node> parsedArgs = ParseList(elements.SubList(1, elements.Count));
            Argument args = new Argument(parsedArgs);
            return new CallNode(func, args, tuple.File, tuple.Start, tuple.End, tuple.Line, tuple.Col);
        }

        private static List<Node> ParseList(List<Node> prenodes)
        {
            List<Node> parsed = new List<Node>();
            foreach (Node s in prenodes)
            {
                parsed.Add(ParseNode(s));
            }
            return parsed;
        }

        // treat the list of nodes as key-value pairs like (:x 1 :y 2)
        private static Map<String, Node> ParseMap(List<Node> prenodes)
        {
            Map<String, Node> ret = new Map<string, Node>();
            if (prenodes.Count % 2 != 0)
            {
                throw new ParserException("must be of the form (:key1 value1 :key2 value2), but got: " +
                        prenodes.ToString(), prenodes[0]);
            }

            for (int i = 0; i < prenodes.Count; i += 2)
            {
                Node key = prenodes[i];
                Node value = prenodes[i + 1];
                if (!(key is KeywordNode))
                {
                    throw new ParserException("key must be a keyword, but got: " + key.ToString(), key);
                }
                ret.Put(((KeywordNode)key).Id, value);
            }
            return ret;
        }

        private static Scope ParseProperties(List<Node> fields)
        {
            Scope properties = new Scope();
            foreach (Node field in fields)
            {
                if (!(field is TupleNode &&
                        ((TupleNode)field).Open.IsDelimType(Constants.SquareBegin) &&
                        ((TupleNode)field).Elements.Count >= 2))
                {
                    throw new ParserException("incorrect form of descriptor: " + field.ToString(), field);
                }
                else
                {
                    List<Node> elements = ParseList(((TupleNode)field).Elements);
                    Node nameNode = elements[0];
                    if (!(nameNode is NameNode))
                    {
                        throw new ParserException("expect a name, but got: " + nameNode.ToString(), nameNode);
                    }
                    String id = ((NameNode)nameNode).Id;
                    if (properties.ContainsKeyLocal(id))
                    {
                        throw new ParserException("duplicated name: " + nameNode.ToString(), nameNode);
                    }

                    Node typeNode = elements[1];
                    if (!(typeNode is NameNode))
                    {
                        throw new ParserException("type must be a name, but got: " + typeNode.ToString(), typeNode);
                    }
                    properties.Put(id, "type", typeNode);

                    Map<String, Node> props = ParseMap(elements.SubList(2, elements.Count));
                    Map<String, Object> propsObj = new Map<string, object>();
                    foreach (var e in props)
                    {
                        propsObj.Put(e.Key, e.Value);
                    }
                    properties.PutProperties(id, propsObj);
                }
            }
            return properties;
        }
    }
}
